// main.c 文件通常是C程序的入口点。
// 编译器会寻找 main 函数来开始执行程序。

// 预处理指令 #include 用于告诉C预处理器在实际编译之前包含其他文件的内容。
// <stdio.h>: Standard Input/Output. 提供了如 printf (打印到控制台) 和文件操作函数。
#include <stdio.h>
// <stdlib.h>: Standard Library. 提供了内存分配 (malloc, free), 字符串转换, 随机数等通用功能。
#include <stdlib.h>
// <string.h>: String operations. 提供了处理字符串的函数，如 strcpy (复制), strlen (长度), strcmp (比较)。
#include <string.h>
// <unistd.h>: UNIX Standard. 提供了对 POSIX 操作系统 API 的访问，例如 fork, exec, usleep (微秒级延时)。
// 注意：这个头文件在非类Unix系统 (如纯Windows) 上可能不可用或功能有限。
#include <unistd.h>
// <time.h>: Time functions. 提供了处理时间和日期的函数，如 time (获取当前时间)。
#include <time.h>

// "lrc.h": 这是项目自定义的头文件，包含了LRC歌词处理相关的结构体定义 (LRC, TIME) 和函数声明。
// 使用双引号 "" 而不是尖括号 <> 表示编译器应首先在当前目录查找此文件。
#include "lrc.h"
// "start_mplayer.h": 项目自定义头文件，声明了与启动 mplayer 播放器相关的函数。
#include "start_mplayer.h"
// "./pos/console.h": 项目自定义头文件，位于 pos 子目录下，声明了控制台操作 (如清屏、光标移动、颜色设置) 的函数。
#include "./pos/console.h"

// 全局变量 (Global Variables) 定义
// 全局变量在所有函数之外声明，它们在程序的整个生命周期内都存在，并且可以被程序中的任何函数访问（除非被局部变量覆盖）。

// m_time: TIME类型的全局变量，并使用 {0,0} 初始化其成员 (minute=0, second=0)。
// TIME 结构体在 "lrc.h" 中定义，用于存储播放的分钟和秒。
// 这个变量会在程序运行中被 m_clock 函数更新，并在 time_printf 函数中使用。
TIME m_time={0,0};

// song_msg: 一个包含4个 char* (字符指针) 的数组，声明为全局变量。
// char* 通常用于表示C语言中的字符串 (指向字符串第一个字符的指针)。
// 这个数组用于存储歌曲的头部信息 (如歌名、歌手、专辑、制作者)。
// lrc_head_resolve 函数会解析这些信息并将其地址存入此数组。
// print_song_message 函数会读取此数组来打印信息。
// song_msg_free 函数会释放这些字符串占用的内存。
char * song_msg[4]; // 原注释: 保存前四行歌曲信息首地址

// main 函数定义：C程序的执行入口。
// int main(...): main 函数通常返回一个 int 类型的值给操作系统，0 表示成功，非0表示错误。
// 参数 argc (argument count): int 类型，表示程序启动时传递给它的命令行参数的数量。
// 参数 argv (argument vector): char *argv[] 类型，一个指向字符指针的数组。
//                           argv[0] 通常是程序的名称，argv[1] 是第一个参数，以此类推。
//                           此程序中未使用 argc 和 argv。
int main(int argc,char *argv[])
{
	// 局部变量 (Local Variables) 声明。这些变量只在 main 函数内部可见和有效。

	// char * lrc_str: 声明一个字符指针 lrc_str。
	// 它将用于指向从LRC歌词文件中读取的全部原始内容的内存地址。
	char * lrc_str;

	// char * lrc_text[200]={NULL}: 声明一个字符指针数组 lrc_text，大小为200。
	// 每个元素 (lrc_text[0], lrc_text[1], ...) 都是一个 char*，可以指向一个字符串。
	// 这个数组将用于存储被 strtok_lrc_buf 函数切割后的每一行歌词的起始地址。
	// 初始化为 {NULL} 表示数组中的所有指针元素初始时都为 NULL (空指针)。
	char * lrc_text[200]={NULL};

	// int line_num: 声明一个整型变量 line_num。
	// 它将存储LRC歌词文件被切割后的总行数。
	int  line_num;

	// int s_time=0: 声明一个整型变量 s_time，并初始化为0。
	// 它用作一个简单的计时器，表示当前歌曲播放的虚拟时间，单位是秒。从0开始计数。
	int s_time=0;

	// LRC *head=NULL, *pb=NULL: 声明两个指向 LRC 结构体的指针 head 和 pb，并都初始化为 NULL。
	// LRC 结构体在 "lrc.h" 中定义，用于表示一句歌词及其时间。
	// head: 将作为歌词双向链表的头指针，指向链表的第一个节点。
	// pb: (play_pointer / current_pointer) 将用于指向当前时间点 s_time 对应的那句歌词的节点。
	LRC *head=NULL,*pb=NULL;

	// LRC *start_point: 声明一个指向 LRC 结构体的指针 start_point。
	// 它将用于指向在屏幕上显示的5句歌词中的第一句歌词的节点地址。
	// find_start_point 函数会计算这个值。
	LRC *start_point;
	// 原注释: 存储模拟时间 (这个注释似乎与变量用途不符，start_point 是LRC节点指针)

	// ---- 歌词文件处理和初始化 ----

	// 1. 读取LRC文件内容:
	// 调用 read_src_file 函数 (在 lrc.c 中定义)，读取指定路径的LRC文件。
	// "./lrc/简单爱.lrc": 相对路径，表示当前目录下的 lrc 子目录中的 "简单爱.lrc" 文件。
	// 函数返回一个指向包含文件内容的字符串的指针，该指针赋值给 lrc_str。
	// 注意：这里硬编码了文件名。更灵活的程序可能会从命令行参数 argv 获取文件名。
	lrc_str=read_src_file("./lrc/简单爱.lrc");
	// 重要的错误处理：检查 read_src_file 是否成功。
	if (lrc_str == NULL) {
		fprintf(stderr, "Error: Failed to read LRC file.\n"); // fprintf(stderr, ...) 用于打印错误消息到标准错误输出
		return 1; // main 返回非0值表示程序出错退出
	}

	// 2. 切割歌词字符串为行:
	// 调用 strtok_lrc_buf 函数 (在 lrc.c 中定义)，将 lrc_str 指向的完整歌词文本
	// 按照换行符切割成多行，并将每一行的起始地址存入 lrc_text 数组中。
	// 函数返回切割出的总行数，存入 line_num。
	// 注意：strtok_lrc_buf 会修改 lrc_str 的内容 (在分隔符处插入 '\0')。
	line_num=strtok_lrc_buf(lrc_text,lrc_str);

	// 3. 解析歌词头部信息:
	// 调用 lrc_head_resolve 函数 (在 lrc.c 中定义)，解析 lrc_text 数组中的前几行，
	// 提取歌名、歌手等信息，并将这些信息的字符串指针存入全局数组 song_msg 中。
	lrc_head_resolve(song_msg,lrc_text);

	// 4. 解析歌词主体内容并构建链表:
	// 调用 lrc_resolve 函数 (在 lrc.c 中定义)，处理 lrc_text 数组中从第4行开始的歌词行，
	// 解析每行的 时间标签 和 歌词文本，为每个时间点创建 LRC 节点，
	// 并将这些节点组织成一个按时间排序的双向链表。链表的头指针赋值给 head。
	head=lrc_resolve(line_num,lrc_text);
	// 重要的错误处理：检查 lrc_resolve 是否成功返回了链表头 (至少应该不为 NULL 如果有歌词)。
	if (head == NULL && line_num > 4) { // 如果有歌词行但链表是空的，可能解析有问题
        fprintf(stderr, "Warning: LRC resolve might have failed, no lyrics data.\n");
        // 可以选择继续（可能没有歌词显示）或退出
    }

	// 5. 释放原始LRC文件内容占用的内存:
	// lrc_str 指向的内存是由 read_src_file 中的 malloc 分配的。
	// 在 lrc_text 和 head 构建完成后，lrc_str 本身不再需要 (其内容已被分割或复制)。
	// 调用 free(lrc_str) 来释放这块内存，防止内存泄漏。
	free(lrc_str);
	lrc_str = NULL; // 释放后将指针设为NULL是个好习惯，防止悬挂指针被误用。

	// ---- 初始化屏幕显示和播放 ----

	// 打印歌曲头部信息 (歌名、歌手等)
	print_song_message(song_msg);
	// 打印初始时间 (通常是 00:00)，m_time 是全局变量，初始为 {0,0}
	time_printf(m_time);
	// 初始时，打印歌词链表的前5句歌词。
	// 第二个参数 NULL 表示此时没有特定的歌词行需要高亮。
	lrc_printf(head,NULL);

	// 调用 mplayer_play 函数 (在 start_mplayer.c 中定义) 启动 mplayer 播放指定的歌曲文件。
	// "./song/简单爱.mp3": 歌曲文件的路径。
	// 这个函数通常会创建一个子进程来播放音乐，主程序可以继续执行。
	mplayer_play("./song/简单爱.mp3");

	// ---- 主循环：同步歌词显示与时间 ----
	// while(1) 创建一个无限循环。循环的退出条件在内部通过 break 语句实现。
	// 这个循环模拟歌曲的播放过程，每秒更新一次时间和显示的歌词。
	while(1)
	{
		// 1. 在屏幕上更新显示当前播放时间 (m_time)。
		time_printf(m_time);

		// 2. 根据当前虚拟播放时间 s_time，在歌词链表 head 中查找对应的歌词节点。
		// link_search (在 lrc.c 中定义) 返回指向匹配节点的指针，或 NULL (如果未找到)。
		pb=link_search(head,s_time);

		// 3. 如果找到了与当前时间 s_time 匹配的歌词节点 (pb 不为 NULL)。
		if(pb!=NULL)
		{
			// a. 确定屏幕上5行歌词的起始显示节点 start_point。
			// find_start_point (在 lrc.c 中定义) 会计算这个节点，使得 pb 尽量居中显示。
			start_point=find_start_point(head,pb);

			// b. 重新打印歌曲头部信息和当前时间。
			// 这可能是为了在歌词滚动刷新时，保持这些信息可见，或者清除旧的歌词行。
			// (更优化的做法可能是只更新变化的部分，而不是全部重绘)
			print_song_message(song_msg); // 这会清屏，所以需要重绘时间
			time_printf(m_time);

			// c. 从 start_point 开始打印5行歌词，并将当前播放的歌词节点 pb 高亮显示。
			lrc_printf(start_point,pb);

			// d. 检查当前播放的歌词 pb 是否是链表的最后一个节点 (pb->next == NULL)。
			if(pb->next==NULL)
				// 如果是最后一句歌词，则使用 break 语句跳出 while(1) 主循环。
				break;
		}

		// 4. 延时。
		// usleep(microseconds) 函数 (来自 <unistd.h>) 用于将程序暂停指定的微秒数。
		// 1000 * 1000 微秒 = 1,000,000 微秒 = 1秒。
		// 这样，主循环大约每秒执行一次。
		usleep(1000*1000);

		// 5. 更新虚拟播放时间。
		// s_time (总秒数) 增加1。
		s_time=s_time+1;
		// 调用 m_clock 函数，更新全局时间结构体 m_time (处理秒到分的进位)。
		// &m_time: 传递 m_time 的地址，因为 m_clock 函数需要修改它。
		m_clock(&m_time);
	} // 主循环结束

	// ---- 程序结束前的清理工作 ----

	// 释放整个歌词链表 (head) 所占用的所有动态分配的内存。
	link_free(head);
	head = NULL; // 设为NULL
	pb = NULL;    // 设为NULL
	start_point = NULL; // 设为NULL

	// 释放存储歌曲头部信息 (song_msg) 的各个字符串所占用的内存。
	song_msg_free(song_msg);

	// 调用 cusor_show() 函数 (在 console.c 中定义)，使控制台的光标重新显示出来。
	// (在程序开始部分，print_song_message 内部可能调用了 cusor_hide() 隐藏了光标)。
	cusor_show();

	// main 函数返回 0，表示程序正常结束。
	return 0;
}